<?php
/***
 * Candy框架 自动验证类		
 * 
 * $Author: 刘森 (fingerboy@qq.com) $
 * $Date: 2019-08-05 01:39:50 $   
 */

declare (strict_types = 1);
namespace Candy\Core;

defined('CANDY') OR die('You Are A Bad Guy. o_O???');
 
final Class Validate {
	static $data;
	static $action;
	static $msg = [];
	static $flag=true;
	static $db=null;
	
	/**
	 * 获取XML内标记的属性，并处理回调内部方法
	 *
	 * @param	resource	$xmlParser	XML的资源
	 * @param	string		$tagName	数据表的名称
	 * @param	array		$args		XML标记的属性		
	 */
	public static function start($xmlParser,string $tagName,array $args): void
	{
		if(isset($args['NAME']) && isset($args['MSG'])){
			if(empty($args['ACTION']) || $args['ACTION']=='both' || $args['ACTION']==self::$action){
				if(is_array(self::$data)){
					if (array_key_exists($args['NAME'],self::$data)){
						if(empty($args['TYPE'])){
							$method='regex';
						}else{
							$method=strtolower($args['TYPE']);
						}
						
						if(in_array($method, get_class_methods(__CLASS__))){
							self::$method(self::$data[$args['NAME']],$args['MSG'],$args['VALUE'],$args['NAME']);
						}else{
							self::$msg[]="验证的规则{$args["TYPE"]} 不存在，请检查！<br>";
							self::$flag=false;
						}
					}else{
						self::$msg[]="验证的字段 {$args["NAME"]} 和表单中的输出域名称不对应<br>";
						self::$flag=false;
					}
				}
			}
		}
	}

	public static function end($xmlParser,string $tagName): bool
	{
		return true;
	}	

	/**
	 * 解析XML文件
	 *
	 * @param	string	$filename	XML的文件名
	 * @param	mixed	$data		表单中输出的数据
	 * @param	string	$action		用户执行的操作add或mod,默认为both
	 * @param	object	$db		数据表的连接对象
	 */
	public static function check(array $data,string $action,object $db): bool
	{
		$token = '';
		//token
		if(array_key_exists('token', $data)){
			$token = $data['token'];
			unset($data['token']);
		}
		
		//csrf
		if(array_key_exists('csrf', $data) && empty($token)){
			$token = $data['csrf'];
			unset($data['csrf']);
		}
		if(isset($_SERVER['HTTP_USER_TOKEN_CSRF']) && empty($token)){
			$token = isset($_SERVER['HTTP_USER_TOKEN_CSRF']);
		}
		
		//常规csrf
		if(empty($token)){
			if(empty($_SERVER['HTTP_REFERER']) || stripos($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST']) === false){
				self::$msg[] = 'CSRF令牌验证失败请重新请求';
				self::$flag = false;
				return false;
			}
		}
		
		$file = substr($db->tabName, strlen(C('TABPREFIX')));
		$xmlfile = $db->path . 'Models/' . ucfirst($file) .'.xml';
		if(file_exists($xmlfile)){
			self::$data=$data;
			self::$action=$action;
			self::$db=$db;
	
			if(is_array($data) && array_key_exists('code', $data)){
				self::vcode($data['code'], '验证码输入<font color=\'red\'>'.$data['code'].'</font>错误！');
			}

			//创建XML解析器
			$xmlParser = xml_parser_create('utf-8');
			
			//使用大小写折叠来保证能在元素数组中找到这些元素名称
			xml_parser_set_option($xmlParser, XML_OPTION_CASE_FOLDING, true);
			xml_set_element_handler ($xmlParser, [__CLASS__,'start'], [__CLASS__, 'end']);
			//读取XML文件
			if (!($fp = fopen($xmlfile, 'r'))){
    			die("无法读取XML文件$xmlfile");
			}

			//解析XML文件
			$has_error = false;			//标志位
			while ($data = fread($fp, 4096)){
				//循环地读入XML文档，只到文档的EOF，同时停止解析
				if (!xml_parse($xmlParser, $data, feof($fp)))
				{
					$has_error = true;
					break;
				}
			}

			if($has_error){ 
				//输出错误行，列及其错误信息
				$error_line   = xml_get_current_line_number($xmlParser);
				$error_row   = xml_get_current_column_number($xmlParser);
				$error_string = xml_error_string(xml_get_error_code($xmlParser));

				$message = sprintf("XML文件 {$xmlfile}［第%d行，%d列］有误：%s", 
					$error_line,
					$error_row,
					$error_string);
					self::$msg[]= $message;
					self::$flag=false;
			}
			//关闭XML解析器指针，释放资源
			xml_parser_free($xmlParser);
		}
		
		//验证token
		self::checkToken($token);
		
		return self::$flag;
	}
	
	/**
	 * 使用自定义的正则表达式进行验证
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 * @param	string	$rules	正则表达式
	 */ 
	private static function regex($value,string $msg,string $rules): void
	{
		if(!preg_match($rules, $value)){
			self::$msg[]=$msg;
			self::$flag=false;
		}
	}
	
	/**
	 * 唯一性验证
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 * @param	string	$name	需要验证的字段名称
	 */ 
	private static function unique($value,string $msg, $rules,string $name): void
	{
		//解决数组更新值唯一
		$info = self::$db->where("$name='$value'")->select(); //查出数据
		if(count($info) > 0){
			if(count($info) == 1 || !empty($rules)){
				//有可能是本身数据
				$rules = strtolower($rules);
				$sqldata = $info[0];
				$data = array_change_key_case(self::$data);
				if($sqldata[$rules] != $data[$rules] || !array_key_exists($rules, $sqldata) || !array_key_exists($rules, $data)){
					self::$msg[]=$msg;
					self::$flag=false;
				}
			}else{
				self::$msg[]=$msg;
				self::$flag=false;
			}
		}
	}
	
	/**
	 *非空验证
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 */ 
	private static function notnull($value,string $msg): void
	{
		if(strlen(trim($value))==0){
			self::$msg[]=$msg;
			self::$flag=false;
		}
	}
	
	/**
	 *Email格式验证
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 */ 
	private static function email($value,string $msg): void
	{
		$rules= "/\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/";

		if(!preg_match($rules, $value)){
			self::$msg[]=$msg;
			self::$flag=false;
		}
	}
	
	/**
	 *URL格式验证
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 */ 
	private static function url($value,string $msg): void
	{
		$rules='/^http\:\/\/([\w-]+\.)+[\w-]+(\/[\w-.\/?%&=]*)?$/';
		
		if(!preg_match($rules, $value)){
			self::$msg[]=$msg;
			self::$flag=false;
		}

	}
	
	/**
	 *数字格式验证
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 */ 
	private static function number($value,string $msg): void
	{
		$rules='/^\d+$/';
		
		if(!preg_match($rules, $value)){
			self::$msg[]=$msg;
			self::$flag=false;
		}
	}
	
	/**
	 * 货币格式验证
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 */ 
	private static function currency($value,string $msg): void
	{
		$rules='/^\d+(\.\d+)?$/';
		
		if(!preg_match($rules, $value)){
			self::$msg[]=$msg;
			self::$flag=false;
		}
	}
	
	/**
	 *验证码自动验证
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 */ 
	private static function vcode($value,string $msg): void
	{		
		if(strtoupper($value) != session('code')){
			self::$msg[]=$msg;
			self::$flag=false;
		}
	}
	
	/**
	 *使用回调用函数进行验证
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 * @param	string	$rules	回调函数名称，回调用函数写在commons下的functions.inc.php
	 */ 
	private static function callback($value,string $msg,string $rules): void
	{
		if(!call_user_func_array($rules, [$value])){
			self::$msg[]=$msg;
			self::$flag=false;
		}
	}
	
	/**
	 *验证两次输出是否一致
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 * @param	string	$rules	对应的另一个表单名称
	 */ 
	private static function confirm($value,string $msg,string $rules): void
	{
		if($value!=self::$data[$rules]){
			self::$msg[]=$msg;
			self::$flag=false;
		}	
	}
	
	/**
	 * 验证数据的值是否在一定的范围内
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 * @param	string	$rules	一个值或多个值，或一个范围
	 */
	private static function in($value,string $msg,string $rules): void
	{
		if(strstr($rules, ',')){
			if(!in_array($value, explode(',', $rules))){
				self::$msg[]=$msg;
				self::$flag=false;
			}	
		}else if(strstr($rules, '-')){
			list($min, $max)=explode('-', $rules);

			if(!($value>=$min && $value <=$max) ){
				self::$msg[]=$msg;
				self::$flag=false;
			}
		}else{
			if($rules!=$value){
				self::$msg[]=$msg;
				self::$flag=false;
			}
		}
	}
	
	/**
	 * 验证数据的值的长度是否在一定的范围内
	 *
	 * @param	string	$value	需要验证的值
	 * @param	string	$msg	验证失败的提示消息
	 * @param	string	$rules	一个范围，例如 3-20(3-20之间)、3,20(3-20之间)、3(必须是3个)、3,(3个以上)
	 */
	private static function length($value,string $msg,string $rules): void
	{
		$fg=strstr($rules, '-') ? '-' : ',';
		if(!strstr($rules, $fg)){
			if(strlen($value) != $rules){
				self::$msg[]=$msg;
				self::$flag=false;
			}
		}else{
			list($min, $max)=explode($fg, $rules);
			if(empty($max)){
				if(strlen($value) < $rules){
					self::$msg[]=$msg;
					self::$flag=false;
				}
			}else if(!(strlen($value)>=$min && strlen($value) <=$max) ){
				self::$msg[]=$msg;
				self::$flag=false;
			}
		}
	}

	/**
	 * 验证失败后的返回提示消息
	 */ 
	public static function getMsg(): string
	{
		$msg=self::$msg;
		self::$msg=[];
		self::$data=null;
		self::$action='';
		self::$flag=true;
		self::$db=null;
		return $msg[0];
	}

	/**
	 * 创建TOKEN 兼顾csrf
	 */
	public static function creatToken(): string
	{
		return \Candy\Extend\Str\Mcrypt::code($_SERVER['HTTP_HOST']);
	}

	/**
	 * 判断TOKEN
	 */
	public static function checkToken(string $token): bool
	{
		if(empty($token)) return true;
		$lastkey = session('CSRFTOKEN');
		$key = \Candy\Extend\Str\Mcrypt::code($token, '', 'de');
		if($lastkey != $token && $key != null && $key == $_SERVER['HTTP_HOST']){
			if(self::$flag){
				session('CSRFTOKEN', $token);
			}
			return true;
		}else{
			self::$msg[] = 'Token令牌无效请刷新后重新提交';
			self::$flag = false;
			return false;
		}
	}

	/**
	 * 加密TOKEN
	 */ 
	private static function authCode(string $token): string
	{
		$key = 'Candy';
		$token = substr(md5($token), 8, 10);
		return md5($key . $token);
	}
}
